package org.mtmi.ui.layouts;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.mtmi.ui.M;
import org.mtmi.ui.controls.Button;
import org.mtmi.ui.controls.Control;
import org.mtmi.ui.scenes.AnimationManager;
import org.mtmi.ui.scenes.COLOR;
import org.mtmi.ui.scenes.FontManager;
import org.mtmi.ui.scenes.Scene;
import org.mtmi.ui.scenes.SceneManager;

/**
 * layout Control in a grid.
 * <p>
 * Control may have {@link GridData} as P_LAYOUT data.
 */
public class GridLayout implements ILayout
{
	private int ncols; //number of columns
	private boolean equals_width;
	private int m_left, m_top, m_right, m_bottom;
	private int h_spacing, v_spacing;
	private int max_row_height;
	
	public GridLayout( int _ncols, boolean _equals_width )
	{
		if( _ncols <=0 ) throw new IllegalArgumentException("columns must be >= 1"); //$NON-NLS-1$
		ncols = _ncols;
		equals_width = _equals_width;
	}
	
	/** set maximum row height allow, <=0 to let controls height */
	public void setMaxRowHeight( int xrh ) {
		max_row_height = xrh;
	}
	
	/** set all margins to given value */
	public void setMargins( int m ) {
		m_left = m_top = m_right = m_bottom = m;
	}
	
	/** set all margins to given values */
	public void setMargins( int left, int top, int right, int bottom ) {
		m_left = left; m_top = top; m_right = right; m_bottom = bottom;
	}
	
	/** change left and right margins */
	public void setHorizontalMargins( int m ) {
		m_left = m_right = m;
	}
	
	/** change top and bottom margins */
	public void setVerticalMargins( int m ) {
		m_top = m_bottom = m;
	}
	
	/** change horizontal and vertical spacing */
	public void setSpacings( int s ) {
		h_spacing = v_spacing = s;
	}
	
	/** change horizontal and vertical spacing */
	public void setSpacings( int horizontal, int vertical ) {
		h_spacing = horizontal;  v_spacing = vertical;
	}
	
	/** change horizontal spacing */
	public void setHorizontalSpacings( int s ) {
		h_spacing = s;
	}
	
	/** change vertical spacing */
	public void setVerticalSpacings( int s ) {
		v_spacing = s;
	}
	
	@Override
	public Point computeSize( LayoutData d, Control[] controls )
	{
		return process( d, controls, false );
	}

	@Override
	public void layout( LayoutData d, Control[] controls )
	{
		process( d, controls, true );
	}
	
	/**
	 * @param d data for layouting
	 * @param controls controls to layout
	 * @param layout true: layout controls, false: return preferred size
	 * @return the preferred size (layout==false)
	 */
	private Point process( LayoutData d, Control []controls, boolean layout )
	{
		if( controls==null ) return null;
		
		//compute number of rows (last may not be fully filled)
		int nrows = 0;
		int c=0;
		for( int i=0; i<controls.length; i++ ) {
			int cs=1;
			if( controls!=null ) {
				Object dta = controls[i].getData( M.D_LAYOUT_DATA );
				if( dta instanceof GridData ) {
					cs = ((GridData)dta).colspan;
				}
			}
			c+=cs;
			if( c > ncols ) {
				throw new IllegalArgumentException("colspan expands more than column count for control:"+controls[i]); //$NON-NLS-1$
			}
			else if( c==ncols ) {
				nrows++;
				c=0;
			}
		}
		if( c>0 ) nrows++;
		
		Point sizes[] = new Point[ controls.length ];
		int cw[] = new int[ ncols ];
		boolean h_grabs[] = new boolean[ ncols ];
		int rh[] = new int[ nrows ];
		boolean v_grabs[] = new boolean[ nrows ];
		int r=0;
		c=0;
		for( int i=0; i<controls.length; i++ ) {
			int cs = 1;
			if( controls[i]!=null ) {
				
				Object data = controls[i].getData( M.D_LAYOUT_DATA );
				if( data instanceof GridData ) {
					GridData gd = (GridData)data;
					cs = gd.colspan;
					if( cs <= 0 ) {
						throw new IllegalArgumentException("colspan must be >=0 for control: "+controls[i] ); //$NON-NLS-1$
					}
					for( int j=0; j<cs && c+j<ncols; j++ ) {
						h_grabs[c+j] |= gd.grab_horizontal;
					}
					v_grabs[r] |= gd.grab_vertical;
				}
				
				Point s = controls[i].computeSize( d );
				sizes[i] = s;
				int w = s.x/cs;
				for( int j=0; j<cs&&j+c<ncols; j++ ) cw[c+j] = Math.max( cw[c+j], w );
				int ch = s.y;
				if( max_row_height > 0 ) ch = Math.min( ch, max_row_height );
				rh[r] = Math.max( rh[r], ch );
			}

			c+=cs;
			if( c>=ncols ) {
				r++; //change row
				c=0;
			}
		}
		
		int tw = 0;
		if( equals_width ) {
			int w = 0;
			for( int i=0; i<cw.length; i++ ) w = Math.max( w, cw[i] );
			for( int i=0; i<cw.length; i++ ) cw[i]=w;
			tw = ncols*w;
		} else {
			for( int i=0; i<cw.length; i++ ) tw += cw[i];
		}
		int th = 0;
		for( int i=0; i<nrows; i++ ) th += rh[i];
		
		int nhgrab = 0; //number of col with horizontal grab
		int nvgrab = 0; //number of row with horizontal grab
		for( int i=0; i<ncols; i++ ) {
			if( h_grabs[i] ) nhgrab++;
		}
		for( int i=0; i<nrows; i++ ) {
			if( v_grabs[i] ) nvgrab++;
		}
		
		int mh = m_left + m_right;
		int hs = (ncols-1)*h_spacing;
		tw += mh + hs;
		int mv = m_top + m_bottom;
		int vs = (nrows-1)*v_spacing;
		th += mv + vs;
		
		//compute size ? we know all what we want
		if( !layout ) {
			return new Point( tw, th );
		}
			
		
		//adjust(expand) width to layouted one ?
		if( d.lw > tw ) {
			if( equals_width ) {
				int w = ( d.lw - mh - hs ) / ncols;
				for( int i=0; i<ncols; i++ ) cw[i]=w;
			} else {
				if( nhgrab>0 ) {
					int dw = ( d.lw-tw ) / nhgrab;
					for( int i=0; i<ncols; i++ ) {
						if( h_grabs[i] ) {
							cw[i] += dw;
						}
					}
				}
			}
		}
		//adjust(expand) height to layouted one ?
		if( d.lh > th ) {
			if( nvgrab>0 ) {
				int dh = ( d.lh-th ) / nvgrab;
				for( int i=0; i<nrows; i++ ) {
					if( v_grabs[i] ) {
						rh[i] += dh;
					}
				}
			}
		}
		
		//now: place control
		c=0;
		r=0;
		int xc = d.lx+m_left;
		int yr = d.ly+m_top;
		for( int i=0; i< controls.length; i++ ) {
			int cs = 1;
			if( controls[i]!=null ) {
				Object dta = controls[i].getData( M.D_LAYOUT_DATA );
				int h_aln = M.LEFT;
				int v_aln = M.TOP;
				if( dta instanceof GridData ) {
					GridData gd = (GridData)dta;
					h_aln = gd.align_horizontal;
					v_aln = gd.align_vertical;
					cs = gd.colspan;
				}


				//layout control in area: xc,yc,cw[c],rh[r]:
				int x,w;
				int tcw = (cs-1)*h_spacing;
				for( int j=0; j<cs&&j+c<ncols; j++ ) tcw += cw[c+j]; //available width
				if( ( h_aln & M.FILL_HORIZONTAL )==M.FILL_HORIZONTAL  || (h_aln & M.FILL ) == M.FILL ) {
					x = xc;
					w = tcw;
				}
				else if( (h_aln & M.CENTER ) == M.CENTER ) {
					w = sizes[i].x;
					x = xc + ( tcw - w )/2;
				}
				else if( (h_aln & M.LEFT ) == M.LEFT ) {
					x = xc;
					w = sizes[i].x;
				}
				else { //RIGHT
					w = sizes[i].x;
					x = xc + tcw - w;
				}
				int y,h;
				int ch = sizes[i].y;
				if( max_row_height > 0 ) ch = Math.min( ch, max_row_height );
				if( ( v_aln & M.FILL_VERTICAL )==M.FILL_VERTICAL || (h_aln & M.FILL ) == M.FILL ) {
					y = yr;
					h = rh[r];
				}
				else if( (v_aln & M.CENTER ) == M.CENTER ) {
					h = ch;
					y = yr + ( rh[r] - h )/2;
				}
				else if( (v_aln & M.TOP ) == M.TOP ) {
					y = yr;
					h = ch;
				}
				else { //BOTTOM
					h = ch;
					y = yr + rh[r] - h;
				}

				controls[i].setBounds( x, y, w, h );
//TODO:debug: System.out.println("Control["+i+"] XXXX c:"+c+" r:"+r+" cs:"+cs+"  @ "+x+","+y+" "+w+"x"+h);
		    }
			
			for( int j=0; j<cs && j+c<ncols; j++ ) xc += cw[c+j] + h_spacing;
			c += cs;
			if( c>=ncols ) {
				yr += rh[r] + v_spacing;
				r++; //change row
				c=0;
				xc = d.lx+m_left;
			}
		}
//System.out.println("For a GridLayout bounds: "+d.lx+","+d.ly+","+d.lw+"x"+d.lh);			
		
		return null;
	}

	//this is to test grid layout, comment this method is there are something wrong there
	public static void main(String[] args)
	{
		final Display display = new Display();
		
		COLOR.initColors( display );
		
		final Shell shell = new Shell(display);
		//shell.setMaximized (true);
		//shell.setFullScreen(true);
		shell.setBackground(display.getSystemColor(SWT.COLOR_BLACK));
		final SceneManager sceneManager = new SceneManager(shell);
		/*final AnimationManager animationManager =*/ new AnimationManager(shell);

		//macOS require a display to start MidiManager
		//MidiManager midiManager = MidiManager.getInstance();
		//midiManager.init();
		
		Scene scene1 = new Scene( shell );
		
		Control controls[] = new Control[10];
		for( int i=0; i<controls.length; i++ ) {
			Button b = new Button( scene1, M.SCENE_CONTROL );
			b.setText( ((i%3)==0 ? "Btn:" : "Button ")+(i+1) );  //$NON-NLS-1$//$NON-NLS-2$
			b.setData( M.D_LAYOUT_DATA, new GridData( M.FILL, (i%3)!=0, M.FILL, true ) );
			controls[i] = b;
		}
		
		GridData gd = (GridData)controls[0].getData( M.D_LAYOUT_DATA );
		gd.colspan = 2;
		
		sceneManager.addScene(scene1);
		
		shell.open();
		
		GC gc = new GC( scene1 );
		try {
			LayoutData ld = new LayoutData( gc, 0, 0, scene1.getSize().x, scene1.getSize().y );
			GridLayout gl = new GridLayout( 4, true );
			//gl.setMargins( 10, 20, 30, 40 );
			gl.setMargins( 20 );
			//gl.setSpacing( 10, 20 );
			gl.setSpacings( 20 );
			gl.layout( ld, controls );
			scene1.redraw();
			
			System.out.println("Layout preferred size:"+gl.computeSize( ld, controls )); //$NON-NLS-1$
		}
		finally {
			gc.dispose();
		}
		
		while (!shell.isDisposed()) {
			if (!shell.isDisposed() && !display.readAndDispatch())
				display.sleep();
		}
		//- dispose allocated colors and fonts
		COLOR.disposeColors();
		FontManager.getInstance().dispose();
		//- dispose the TUIO client
		//: TUIOManager.getInstance().dispose();
		//- dispose MIDI stuff
		//: MidiManager.getInstance().dispose();
		display.dispose();
	}
}
